package com.ajview.core.config;

import com.ajview.common.core.constant.Constants;
import com.ajview.core.security.filter.JwtAuthenticationTokenFilter;
import com.ajview.core.security.handler.AccessDeniedHandlerImpl;
import com.ajview.core.security.handler.AuthenticationEntryPointImpl;
import com.ajview.core.security.handler.LogoutSuccessHandlerImpl;
import com.ajview.core.security.service.UserDetilServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.argon2.Argon2PasswordEncoder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder;
import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import javax.annotation.Resource;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
 * @author ZhangJunJie
 * @Date 2022-04-27
 * <p>
 * 概要：SpringSecurity 安全框架核心配置类
 */
@Slf4j
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * 自定义认证逻辑
     */
    @Resource
    private UserDetilServiceImpl userDetilServiceImpl;
    /**
     * 身份认证 入口 处理无权限访问和匿名访问验证
     */
    @Resource
    private AuthenticationEntryPointImpl authenticationEntryPointImpl;
    @Resource
    private AccessDeniedHandlerImpl accessDeniedHandlerImpl;
    /**
     * jwtToken 验证拦截器
     */
    @Resource
    private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
    /**
     * 退出登录
     */
    @Resource
    private LogoutSuccessHandlerImpl logoutSuccessHandlerImpl;


    /**
     * 自定义认证逻辑配置到SpringSecurity认证
     *
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetilServiceImpl);
    }


    /**
     * 暴露 AuthenticationManager 到工厂
     * 解决 无法直接注入 AuthenticationManager
     *
     * @return
     * @throws Exception
     */
    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }


    /**
     * 自定义密码加密处理器（加密方式组和默认加密方）
     *
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        Map<String, PasswordEncoder> encoders = new HashMap<>(15);
        encoders.put("bcrypt", new BCryptPasswordEncoder());
        encoders.put("ldap", new org.springframework.security.crypto.password.LdapShaPasswordEncoder());
        encoders.put("md4", new org.springframework.security.crypto.password.Md4PasswordEncoder());
        encoders.put("md5", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("MD5"));
        encoders.put("noop", org.springframework.security.crypto.password.NoOpPasswordEncoder.getInstance());
        encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
        encoders.put("scrypt", new SCryptPasswordEncoder());
        encoders.put("sha-1", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-1"));
        encoders.put("sha-256", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-256"));
        encoders.put("sha256", new org.springframework.security.crypto.password.StandardPasswordEncoder());
        encoders.put("argon2", new Argon2PasswordEncoder());
        return new DelegatingPasswordEncoder(Constants.ID_FOR_ENCODE, encoders);
    }


    /**
     * 配置SpringSecurity 跨域解决方案
     *
     * @return
     */
    private CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.setAllowedHeaders(Arrays.asList("*"));
        corsConfiguration.setAllowedMethods((Arrays.asList("GET", "POST", "DELETE", "PUT", "OPTIONS")));
        corsConfiguration.setAllowedOrigins(Arrays.asList("*"));
        corsConfiguration.setMaxAge(3600L);
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", corsConfiguration);
        return source;
    }

    /**
     * SpringSecurity 核心配置
     *
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //关闭SpringSecurity 自己的csrf 我们这里使用Token解决CSRF
        http.csrf().disable()
                // 禁用 session 基于token，所以不需要session【但禁用后，将导致UserDetailsService每次都会执行，无法缓存用户信息】
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                .authorizeRequests()
                // 登录  注册  验证码  允许所有人访问
                .antMatchers("/sysLogin/login", "/register", "/captcha/captchaCode.json").permitAll()
                // 放行静态资源文件
                .antMatchers(
                        HttpMethod.GET,
                        "/*.html",
                        "/**/*.html",
                        "/**/*.css",
                        "/**/*.js",
                        "/profile/**"
                ).permitAll()
                .antMatchers("/swagger-ui.html").anonymous()
                .antMatchers("/swagger-resources/**").anonymous()
                .antMatchers("/druid/**").anonymous()
                // 除了上面放行的请求以外，都需要认证鉴权
                .anyRequest().authenticated()
//                .anyRequest().access("@prs.hasPermissions(authentication,request)")
                .and()
                .logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandlerImpl)
                //跨域解决方案
                .and().cors().configurationSource(corsConfigurationSource());

        // 自定义异常的处理器，将执行未鉴权的处理方法
        http.exceptionHandling()
                //认证异常
                .authenticationEntryPoint(authenticationEntryPointImpl)
                //权限异常
                .accessDeniedHandler(accessDeniedHandlerImpl);


        //addFilterAt :用什么Filter替换过滤器链中的那个Filter
        //addFilterBefore: 放在那个Filter之前
        //addFilterAfter: 放在那个Filter之后

        //认证之前先进行token验证
        http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);

        // 禁用缓存
        http.headers().cacheControl();
    }
}
